package com.gitee.kenewstar.migration.function;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.gitee.kenewstar.migration.constants.Const;
import com.gitee.kenewstar.migration.constants.FunctionCode;
import com.gitee.kenewstar.migration.core.AbstractSyncFunction;
import com.gitee.kenewstar.migration.core.ContextParam;
import com.gitee.kenewstar.migration.entity.CommonResult;
import com.gitee.kenewstar.migration.entity.JobCronTriggers;
import com.gitee.kenewstar.migration.entity.JobDetails;
import com.gitee.kenewstar.migration.entity.JobExecutor;
import com.gitee.kenewstar.migration.entity.JobInfo;
import com.gitee.kenewstar.migration.entity.JobTriggers;
import com.gitee.kenewstar.migration.service.JobService;
import com.gitee.kenewstar.migration.util.Util;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author kenewstar
 */
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class JobFunction extends AbstractSyncFunction<JobInfo> {

    private final JobService jobService;

    @Override
    protected Class<JobInfo> parameterType() {
        return JobInfo.class;
    }

    private Map<Long, Long> getExecutorMap(List<JobInfo> jobInfoList) {
        // 执行器ID
        final List<Long> executorIds = jobInfoList.stream().map(JobInfo::getExecutorId).distinct().collect(Collectors.toList());
        final List<JobExecutor> jobExecutors = this.jobService.devSelectExecutor(executorIds);
        final List<String> executorCodes = jobExecutors.stream().map(JobExecutor::getExecutorCode).map(Util::getExecutor).collect(Collectors.toList());
        final List<JobExecutor> uatExecutors = this.jobService.uatSelectExecutor(executorCodes);
        Map<String, Long> uatCodeAndIdMap = new HashMap<>(uatExecutors.size());
        uatExecutors.forEach(v -> uatCodeAndIdMap.put(v.getExecutorCode(), v.getExecutorId()));
        // 执行器映射
        Map<Long, Long> devAndUatJobIdMap = new HashMap<>(16);
        jobExecutors.forEach(e -> {
            final String targetExecutor = Util.getExecutor(e.getExecutorCode());
            // 获取目标执行器ID
            final Long targetExecutorId = uatCodeAndIdMap.get(targetExecutor);
            if (Objects.nonNull(targetExecutorId)) {
                devAndUatJobIdMap.put(e.getExecutorId(), targetExecutorId);
            }
        });
        return devAndUatJobIdMap;
    }

    @Override
    public List<CommonResult> execute(ContextParam contextParam, List<JobInfo> list) {
        // 获取job信息
        final List<JobInfo> jobInfoList = this.jobService.devSelectJobInfoList(list);
        if (CollUtil.isEmpty(jobInfoList)) {
            return Collections.emptyList();
        }
        // 执行器映射
        final Map<Long, Long> devAndUatExeIdMap = this.getExecutorMap(jobInfoList);


        // job_id, tenant_id
        final List<Long> ids = jobInfoList.stream().map(JobInfo::getJobId).collect(Collectors.toList());
        final List<Long> tenantIds = jobInfoList.stream().map(JobInfo::getTenantId).distinct().collect(Collectors.toList());

        final List<JobDetails> jobDetails = this.jobService.devSelectJobDetails(ids, tenantIds);
        final Map<String, List<JobDetails>> jobDetailsMap = jobDetails.stream().collect(Collectors.groupingBy(v -> v.getJobId() + "-" + v.getTenantId()));

        final List<JobTriggers> jobTriggers = this.jobService.devSelectJobTriggers(ids, tenantIds);
        final Map<String, List<JobTriggers>> jobTriggersMap = jobTriggers.stream().collect(Collectors.groupingBy(v -> v.getJobId() + "-" + v.getTenantId()));

        final List<JobCronTriggers> jobCronTriggers = this.jobService.devSelectJobCronTriggers(ids, tenantIds);
        final Map<String, List<JobCronTriggers>> jobCronTriggersMap = jobCronTriggers.stream().collect(Collectors.groupingBy(v -> v.getJobId() + "-" + v.getTenantId()));

        contextParam.jobContext = Context.builder().build();
        contextParam.jobContext.devAndUatExeIdMap = devAndUatExeIdMap;
        contextParam.jobContext.jobDetailsMap = jobDetailsMap;
        contextParam.jobContext.jobTriggersMap = jobTriggersMap;
        contextParam.jobContext.jobCronTriggersMap = jobCronTriggersMap;

        List<CommonResult> resultList = new ArrayList<>(jobInfoList.size());
        for (JobInfo jobInfo : jobInfoList) {

            try {
                contextParam.jobContext.jobInfo = jobInfo;
                this.self().singleExecute(contextParam);
                resultList.add(CommonResult.create(true, jobInfo.getTenantId()).setJobCode(jobInfo.getJobCode()));
            } catch (Exception e) {
                resultList.add(CommonResult.create(false, jobInfo.getTenantId()).setJobCode(jobInfo.getJobCode()).setErrorMsg(e.getMessage()));
                e.printStackTrace();
            }

        }

        return resultList;
    }

    @Override
    public FunctionCode functionCode() {
        return FunctionCode.JOB;
    }

    @Override
    @DS(Const.UAT)
    @Transactional(rollbackFor = Exception.class)
    public void singleExecute(ContextParam contextParam) {
        JobInfo jobInfo = contextParam.jobContext.jobInfo;
        Map<Long, Long> devAndUatExeIdMap = contextParam.jobContext.devAndUatExeIdMap;
        Map<String, List<JobDetails>> jobDetailsMap = contextParam.jobContext.jobDetailsMap;
        Map<String, List<JobTriggers>> jobTriggersMap = contextParam.jobContext.jobTriggersMap;
        Map<String, List<JobCronTriggers>> jobCronTriggersMap = contextParam.jobContext.jobCronTriggersMap;

        final Long devId = jobInfo.getJobId();
        final Long devTenantId = jobInfo.getTenantId();
        final String key = devId + "-" + devTenantId;

        final List<JobTriggers> triggers = jobTriggersMap.get(key);
        // 无触发器，标识数据过滤，不执行，也不报错
        Assert.notEmpty(triggers, "无触发器, 该任务可能已结束，请检查");

        final Long uatExecutorId = devAndUatExeIdMap.get(jobInfo.getExecutorId());
        Assert.notNull(uatExecutorId, "目标执行器不存在");
        jobInfo.setJobId(null);
        jobInfo.setExecutorId(uatExecutorId);
        this.jobService.uatInsertJobInfo(ListUtil.toList(jobInfo));

        final List<JobDetails> details = jobDetailsMap.get(key);
        if (CollUtil.isNotEmpty(details)) {
            details.forEach(v -> v.setJobId(String.valueOf(jobInfo.getJobId())));
            this.jobService.uatInsertJobDetails(details);
        }

        triggers.forEach(v -> v.setJobId(String.valueOf(jobInfo.getJobId())));
        triggers.forEach(v -> v.setJobName(String.valueOf(jobInfo.getJobId())));
        this.jobService.uatInsertTriggers(triggers);


        final List<JobCronTriggers> cronTriggers = jobCronTriggersMap.get(key);
        if (CollUtil.isNotEmpty(cronTriggers)) {
            cronTriggers.forEach(v -> v.setJobId(String.valueOf(jobInfo.getJobId())));
            this.jobService.uatInsertCronTriggers(cronTriggers);
        }

    }

    @Builder
    @SuppressWarnings("all")
    public static class Context {
        public JobInfo jobInfo;
        public Map<Long, Long> devAndUatExeIdMap;
        public Map<String, List<JobDetails>> jobDetailsMap;
        public Map<String, List<JobTriggers>> jobTriggersMap;
        public Map<String, List<JobCronTriggers>> jobCronTriggersMap;

    }
}
